#!/bin/bash
# * =====================================================
# * Copyright © hk. 2025-2099. All rights reserved.
# * File name  : common.sh
# * Author     : 苏木
# * Date       : 2025-10-19
# * ======================================================
##


# 脚本和工程路径
# ========================================================
# 下面这几个路径会跟着脚本变化，其他的都会调用这个脚本，这里采用其他名字防止冲突
# 在source script这个命令就大概类似于把这个文件插入到调用的地方，同名变量谁后出现使用谁的值
COMMON_SCRIPT_NAME=${0#*/}
COMMON_SCRIPT_CURRENT_PATH=${0%/*}
COMMON_SCRIPT_ABSOLUTE_PATH=`cd $(dirname ${0}); pwd`
COMMON_SCRIPT_ABSOLUTE_PATH=${COMMON_SCRIPT_ABSOLUTE_PATH} # 工程的源码目录，一定要和编译脚本是同一个目录

# 下面这几个路径是写死的，在所有文件中可用
SOFTWARE_DIR_PATH=~/2software        # 软件安装目录
TFTP_DIR=~/3tftp
NFS_DIR=~/4nfs
CPUS=$(($(nproc)-1))                 # 使用总核心数-1来多线程编译

# 打开错误状态监测
# ========================================================
# -e 表示当脚本中的任何命令以 ‌非零状态（失败）‌ 退出时，立即终止脚本执行
# -E 确保 ERR 陷阱（trap）在 ‌函数、子 Shell 或命令替换‌ 中生效
set -eE

# 可用的emoji符号
# ========================================================
# https://www.w3school.com.cn/charsets/ref_emoji.asp
function usage_emoji()
{
    echo -e "⚠️ ✅ ❌ 🚩 📁 🕣️ 🔔"
}

# 颜色和日志标识
# ========================================================
# |      ---       |Black |  Red | Green | Yellow | Blue | Magenta | Cyan | White |
# | Fore(Standard) |  30  |  31  |  32   |   33   |  34  |   35    |  35  |   37  |
# | Fore(light)    |  90  |  91  |  92   |   93   |  94  |   95    |  95  |   97  |
# | Back(Standard) |  40  |  41  |  42   |   43   |  44  |   45    |  46  |   47  |
# | Back(light)    | 100  | 101  | 102   |  103   | 104  |  105    | 106  |  107  |
BLACK="\033[1;30m"
RED='\033[1;31m'    # 红
GREEN='\033[1;32m'  # 绿
YELLOW='\033[1;33m' # 黄
BLUE='\033[1;34m'   # 蓝
PINK='\033[1;35m'   # 紫
CYAN='\033[1;36m'   # 青
WHITE='\033[1;37m'  # 白
CLS='\033[0m'       # 清除颜色

INFO="${GREEN}INFO: ${CLS}"
WARN="${YELLOW}WARN: ${CLS}"
ERROR="${RED}ERROR: ${CLS}"

# 日志打印
# ========================================================
# Export global functions
set -a # 自动导出，相当于export

function sh_log()
{
    LOG_COLOR="$1"           # 获取要显示的前景色
    shift                    # 参数左移
    if [ "$1" = "-n" ]; then # -n表示取消echo的换行
        shift
        LOG_FLAG="-ne"
    else
        LOG_FLAG="-e"
    fi
    echo $LOG_FLAG "\e[${LOG_COLOR}m$@\e[0m"

}

function prt()
{
    sh_log 0 "$@" # default
}

function message()
{
    sh_log 36 "$@" # light blue
}

function notice()
{
    echo -n "✨ "
    sh_log 35 "$@" # purple
}

function warning()
{
    echo -n "⚠️  "
    sh_log 33 "$@" # dark yellow
}

function error()
{
    echo -n "🔖 "
    sh_log 91 "$@" # light red
}

function success()
{
    echo -n "✅ "
    sh_log 32 "$@" # light red
}

function fatal()
{
    echo -n "🔖 "
    sh_log 31 "$@" # dark red
}

function info()
{
    echo -ne "${INFO}"
    sh_log 0 "$@" # dark red
}

function log_echo_demo()
{
    info "log help:"
    prt -n "Log colors:|"
    message -n " message |"
    success -n " success |"
    notice -n " notice |"
    warning -n " warning |"
    error -n " error |"
    fatal " fatal |"
    echo
}
set +a
# End of global functions

# 调试信息
# ========================================================
HOOK_RET=0    # 存储命令的状态
trap 'err_handler' ERR # trap 是用于信号处理的内置命令，可捕获系统信号或脚本事件并执行指定操作，常用于实现资源清理、中断处理和调试功能
                       #  EXIT    0    脚本正常退出时触发
                       #  ERR    -    命令返回非零状态码时触发，但是若命令返回值被捕获的话就不会触发
                       #            例如 false || exit_code=1
function err_handler()
{
    ret=${1:-$?}
    if [ "$ret" -eq 0 ]; then
        return 0
    fi

    fatal "ERROR: Running $BASH_SOURCE - ${2:-${FUNCNAME[1]}} failed!"
    fatal "ERROR: exit code $ret from line ${BASH_LINENO[0]}:"
    fatal "    ${3:-$BASH_COMMAND}"
    fatal "ERROR: call stack:"
    for i in $(seq 1 $((${#FUNCNAME[@]} - 1))); do
        SOURCE="${BASH_SOURCE[$i]}"
        LINE=${BASH_LINENO[$(( $i - 1 ))]}
        fatal "    $(basename "$SOURCE"): ${FUNCNAME[$i]}($LINE)"
    done
    exit $ret
}

function err_handler_demo()
{
    false                  # 这种写法会直接触发trap注册的处理函数
    # HOOK_RET=${PIPESTATUS[0]}
    # if [ $HOOK_RET -ne 0 ]; then
    #     err_handler $HOOK_RET "${FUNCNAME[0]} $*" "$@"
    #     exit $HOOK_RET
    # fi
}

function err_handler_demo_test()
{
    err_handler_demo err_test_demo 1
}

# 时间计算
# ========================================================
TIME_START=
TIME_END=

function get_start_time()
{
    TIME_START=$(date +'%Y-%m-%d %H:%M:%S')
}

function get_end_time()
{
    TIME_END=$(date +'%Y-%m-%d %H:%M:%S')
}

function get_execute_time()
{
    start_seconds=$(date --date="$TIME_START" +%s);
    end_seconds=$(date --date="$TIME_END" +%s);
    duration=`echo $(($(date +%s -d "${TIME_END}") - $(date +%s -d "${TIME_START}"))) | awk '{t=split("60 s 60 m 24 h 999 d",a);for(n=1;n<t;n+=2){if($1==0)break;s=$1%a[n]a[n+1]s;$1=int($1/a[n])}print s}'`
    echo "===*** 🕣️ 运行时间：$((end_seconds-start_seconds))s,time diff: ${duration} ***==="
}

function time_count_down
{
    for i in {3..0}
    do     

        echo -ne "${INFO}after ${i} is end!!!"
        echo -ne "\r\r"        # echo -e 处理特殊字符  \r 光标移至行首，但不换行
        sleep 1
    done
    echo "" # 打印一个空行，防止出现混乱
}

function get_run_time_demo()
{
    get_start_time
    time_count_down
    get_end_time
    get_execute_time
}

# 目录切换函数定义
# ========================================================
function cdi()
{
    if command -v pushd &>/dev/null; then
        # 压栈并切换
        pushd $1 >/dev/null || return 1
    else
        cd $1
    fi
}

function cdo()
{
    if command -v popd &>/dev/null; then
        # 弹出并恢复
        popd >/dev/null || return 1
    else
        cd -
    fi
}

function cdi_cdo_demo()
{
    echo "=== Testing cdi and cdo commands ==="
    mkdir -p ~/a/b
    cdi ~/a
    echo "after cdi ~/a: $(pwd)"
    cdi b
    echo "after cdi b: $(pwd)"
    cdo
    echo "after cdo: $(pwd)"
    cdo
    echo "after cdo: $(pwd)"

    echo ""
    cdi ~/a/b
    echo "after cdi ~/a/b: $(pwd)"
    cdo
    echo "after cdo: $(pwd)"
    rm -rf ~/a
    echo ""
}

# 命令执行函数定义(需要添加sudo权限的命令可以用这个命令执行来防止需要输入命令)
# ========================================================
# execute执行语句成功与否打印,此命令不会触发,主要是用于一些需要sudo的命令，这里可以自动判断
function execute() 
{
    local PASSWD="000000"
    local execute_exit_code=0
    local cmd_str

    # local cmd_str_src
    # printf -v cmd_str_src "%q " "$@" # 构造安全的命令字符串,不转义特殊字符

    # 重组所有参数为命令字符串（保留用户输入的转义符号）
    cmd_str=$(printf "%s " "$@" | sed 's/ $//')
    cmd_str=${cmd_str% }  # 移除末尾空格

    # 打印命令（高亮提示）
    if [ "$EUID" -ne 0 ]; then
        printf "\033[36mcmd:\033[0m sudo %s\n" "$cmd_str"
    else
        printf "\033[36mcmd:\033[0m %s\n" "$cmd_str"
    fi

    # 执行命令（通过 bash -c 解析复杂命令）
    if [ "$EUID" -ne 0 ]; then
        echo "$PASSWD" | sudo -S bash -c "$cmd_str" || execute_exit_code=1
    else
        bash -c "$cmd_str" || execute_exit_code=1
    fi

    # 错误处理
    if [ $execute_exit_code -ne 0 ]; then
        error "执行 $cmd_str 失败!"
        return 1 # trap 仅对当前 Shell 进程有效，子进程继承独立的信号处理环境，父进程无法感知子进程的 exit 行为
                 # 这里用return可以保持进程统一性，确保 trap 生效
    fi
}

function execute_demo()
{
    
    # execute false # 用于测试出错后被tap捕获
    execute apt-get update # 加上sudo执行

    local str=sumu1
    execute echo "${str}" \> /etc/demo.txt
    info "demo.txt内容如下:"
    cat /etc/demo.txt
    echo

    str=sumu2
    execute echo "${str}" \| tee -a /etc/demo.txt
    info "demo.txt内容如下:"
    cat /etc/demo.txt
    echo

    str=sumu3
    execute echo "${str}" \>\> /etc/demo.txt
    info "demo.txt内容如下:"
    cat /etc/demo.txt

    execute rm -rvf /etc/demo.txt
    echo

}

# 开发环境信息
# ========================================================
function get_sys_info()
{
    local kernel_version=$(uname -r) # 获取内核版本信息，-a选项会获得更详细的版本信息
    local sys_version=$(grep -oP 'PRETTY_NAME="\K[^"]+' /etc/os-release) # 从/etc/os-release获取sys版本信息

    
    local sys_ram_total=$(cat /proc/meminfo |grep 'MemTotal' |awk -F : '{print $2}' |sed 's/^[ \t]*//g')   # 获取sys RAM大小
    local sys_swap_total=$(cat /proc/meminfo |grep 'SwapTotal' |awk -F : '{print $2}' |sed 's/^[ \t]*//g') # 获取sys 交换空间swap大小
    #local sys_disk=$(sudo fdisk -l |grep 'Disk' |awk -F , '{print $1}' | sed 's/Disk identifier.*//g' | sed '/^$/d') #显示硬盘，以及大小
    local sys_cpu=$(grep 'model name' /proc/cpuinfo |uniq |awk -F : '{print $2}' |sed 's/^[ \t]*//g' |sed 's/ \+/ /g') #cpu型号
    local sys_physical_id=$(grep 'physical id' /proc/cpuinfo |sort |uniq |wc -l) #物理cpu个数
    local sys_cpu_cores=$(grep 'cpu cores' /proc/cpuinfo |uniq |awk -F : '{print $2}' |sed 's/^[ \t]*//g') #物理cpu内核数
    local sys_processor=$(grep 'processor' /proc/cpuinfo |sort |uniq |wc -l) #逻辑cpu个数(线程数)
    local sys_cpu_mode=$(getconf LONG_BIT) #查看CPU当前运行模式是64位还是32位

    # 打印结果
    prt "sys   : $sys_version - $sys_cpu_mode"
    prt "kernel: $kernel_version"
    prt "ram   : $sys_ram_total"
    prt "swap  : $sys_swap_total"
    prt "cpu   : $sys_cpu,physical id is$sys_physical_id,cores is $sys_cpu_cores,processor is $sys_processor"
}

# 本地虚拟机VMware开发环境信息
function get_dev_env_info()
{
    prt "Development environment: "
    prt "ubuntu : 20.04.2-64(1核12线程 16GB RAM,512GB SSD) arm"
    prt "VMware : VMware® Workstation 17 Pro 17.6.0 build-24238078"
    prt "Windows: "
    prt "          处理器 AMD Ryzen 7 5800H with Radeon Graphics 3.20 GHz 8核16线程"
    prt "          RAM    32.0 GB (31.9 GB 可用)"
    prt "          系统类型    64 位操作系统, 基于 x64 的处理器"
    prt "linux开发板原始系统组件版本:"
    prt "          uboot : v2019.04 https://github.com/nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0"
    prt "          kernel: v4.19.71 https://github.com/nxp-imx/linux-imx/releases/tag/v4.19.71"
    prt "          rootfs: buildroot-2023.05.1 https://buildroot.org/downloads/buildroot-2023.05.1.tar.gz"
    prt ""
    prt "x86_64-linux-gnu   : gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.2)"
    prt "arm-linux-gnueabihf:"
    prt "          arm-linux-gnueabihf-gcc 8.3.0"
    prt "          https://developer.arm.com/-/media/Files/downloads/gnu-a/8.3-2019.03/binrel/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf.tar.xz"
}

# 环境变量
# ========================================================
# Github Actions托管的linux服务器有以下用户级环境变量，系统级环境变量加上sudo好像也权限修改
# .bash_logout  当用户注销时，此文件将被读取，通常用于清理工作，如删除临时文件。
# .bashrc       此文件包含特定于 Bash Shell 的配置，如别名和函数。它在每次启动非登录 Shell 时被读取。
# .profile、.bash_profile 这两个文件位于用户的主目录下，用于设置特定用户的环境变量和启动程序。当用户登录时，
#                        根据 Shell 的类型和配置，这些文件中的一个或多个将被读取。
USER_ENV=(~/.bashrc ~/.profile ~/.bash_profile)
SYSENV=(/etc/profile) # 系统环境变量位置
ENV_FILE=("${USER_ENV[@]}" "${SYSENV[@]}")

function source_env_info()
{
    for temp in ${ENV_FILE[@]};
    do
        if [ -f ${temp} ]; then
            prt "${INFO}source ${temp}"
            source ${temp}
        fi
    done
}


# 测试函数
# ========================================================
function echo_path()
{
    echo "路径定义:"
    echo "COMMON_SCRIPT_NAME: ${COMMON_SCRIPT_NAME}"
    echo "COMMON_SCRIPT_CURRENT_PATH: ${COMMON_SCRIPT_CURRENT_PATH}"
    echo "COMMON_SCRIPT_ABSOLUTE_PATH: ${COMMON_SCRIPT_ABSOLUTE_PATH}"
    echo "COMMON_SCRIPT_ABSOLUTE_PATH: ${COMMON_SCRIPT_ABSOLUTE_PATH}"
    echo "SOFTWARE_DIR_PATH: ${SOFTWARE_DIR_PATH}"
    echo "TFTP_DIR: ${TFTP_DIR}"
    echo "NFS_DIR: ${NFS_DIR}"
    echo "CPUS: ${CPUS}"
    
}

function common_demo()
{
    EXECUTE_MODE=manu
    DEMO_NUM=0
    notice "There are $# parameters: $@ (\$1~\$$#)"
    while getopts "ahp:d:" arg #选项后面的冒号表示该选项需要参数
    do
        case ${arg} in
            a) EXECUTE_MODE="auto" ;;
            d) DEMO_NUM=${OPTARG} ;;
            h) log_echo_demo ;;
            p)
                if [ $OPTARG == "1" ];then
                    message "OPTARG=$OPTARG"
                fi
                ;;
            
            ?)  #当有不认识的选项的时候arg为?
                error "unkonw argument..."
                exit 1
                ;;
        esac
    done

    # 根据参数执行对应的demo
    case ${DEMO_NUM} in
        0) echo_path ;;
        1) get_run_time_demo ;;
        2) execute_demo ;;
        3) get_sys_info ;;
        4) get_dev_env_info ;;
        5) cdi_cdo_demo ;;
        6) err_handler_demo_test ;;
        *)
            error "unkonw demo num..."
            return 1
            ;;
    esac 
}
# 判断脚本是否直接执行
# ========================================================
if [ "$0" = "$BASH_SOURCE" ]; then
    common_demo "$@"
fi



